https://www.youtube.com/watch?v=Z76QlSpYcck&list=PLOETEcp3DkCpimylVKTDe968yNmNIajlR&index=16
Data Composition with RxJS | Deborah Kurata
我這一篇算是
Day25_[S05E07] CaseStudy: RxJS真實案例展示
的延申閱讀
在[S05E07],jiaming大大分享怎麼從多個Web API傳回來的observable
用RxJS來重組成我們需要的資料
我覺得跟這一篇有關
Data Composition with RxJS | Deborah Kurata
如果你有興趣,可以直接看影片,有英文字幕,
這一集相較custom operator,比較沒那麼硬,
而且將HttpClient跟後端要到資料後的組合,非常實用!!
投影片
http://bit.ly/deborahk-ngconf2019
原始碼
https://github.com/DeborahK/Angular-DD
Twitter:
deborahkurata
Declarative approach to
@Injectable({ providedIn:'root' })
export class ProductService{
private productsUrl='api/products';
constructor(private http: HttpClient){}
getProducts():Observable<Product[]>{
return this.http.get<Product[]>(this.productsUrl)
.pipe(
tap(console.log),
catchError(this.handleError)
);
}
}
export class ProductListComponent implements OnInit{
products$=this.productService.products$
.pipe(
catchError(error=>{
this.errorMessage=error;
return of(null); // 是不是類似 return EMPTY ?
})
);
constructor(private productService: ProductService){}
}
<div *ngIf="products$ | async as products">
^^^^^ 會自動訂閱及取消訂閱
<button type='button'
*ngFor='let product of products'>
{{ product.productName }} ({{ product.category }})
</button>
</div>
ChangeDetectionStrategy.OnPush
@Component({
selector: 'pm-product-list',
templateUrl: './product-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
^^^^^^
})
OnPush 改善效能(縮小change detection cycles)
只有在下列情況下會檢查
@Injectable({ providedIn:'root' })
export class ProductCategoryService{
private productCategoriesUrl='api/productCategories';
constructor(private http: HttpClient){}
productCategories$ = this.http.get<ProductCategory[]>(this.productCategoriesUrl)
.pipe(
tap(console.log),
catchError(this.handleError)
);
}
}
RxJS Creation Function: combineLatest()
Uses the latest emitted values of each of its input streams
Waits for each
input stream to emit at least once before it starts emitting
Emits an array of values, in order, one for each input stream
productsWithCategory$ = combineLatest(
^^^^^^^^^^^^^
this.products$,
this.ProductCategoryService.productCategories$
) // 類似 [ Products[], ProductCategories[] ]
.pipe( vvvvvvvvvvvvvvvvvvvvvvvv Array destructuring
map( ([products, categories]) =>
VVV Array map method
products.map(
p=>
(
{ VVV Spread Operator copies over the properties
...p,
category: categories.find(c=>
p.categoryId === c.id).name
} as Product
)
)
)
)
以上是類似
Day25_[S05E07] CaseStudy: RxJS真實案例展示
資料的重組
接下來是其他的資料流重組
Create an action stream
Combine action and data streams
Emit a value every time an action occurs
Creating an Action Stream
private productSelectedAction = new Subject<number>();
productSelectedAction$=this.productSelectedAction.asObservable();
^^^^^^^^^^^^^^
// Observable
// Observer:next,error,complete
selectedProduct$=combineLatest(
this.productSelectedAction, // action stream (使用者選了某product)
this.productsWithCategory$
).pipe(
map( ([selectedProductId, products] ) =>
products.find(product => product.id === selectedProductId)
^^^^^^^^^^^^^^^^^
)
);
// 當使用者選擇product的時候
onSelected(productId: number): void{
this.productService.changeSelectedProduct(productId);
} ^^^^^^^^^^^^^^^^^^^^^ 告知service,productId變了
// service來操作observable$
changeSelectedProduct(selectedProductId: number | null):void{
// emits that next productId
this.productSelectedAction.next(selectedProductId);
^^^^^^^^^^^^^^^^ emitting a value on action
}
// marble diagram
data$ : ----[ {saw},{rake},{axe} ]----
action$: --1-------------------------------14-
combineLatest(data$,action$) // 抓2個最新的,回傳新的Observable
----[ [{saw},{rake},{axe}] , 1 ]--[ [{saw},{rake},{axe}] , 14 ]
Cache the Array?
只能透過service分享arrar?
@Injectable({ providedIn: 'root' })
export class ProductService{
private productsUrl='api/products';
products: Product[];
products$=this.http.get<Product[]>(this.productsUrl)
.pipe(
map(data=>this.products=data),
catchError(this.handleError)
);
constructor(private http: HttpClient){}
}
RxJS operator: shareReplay
Shares an Observable with all subscribers
products$=this.http.get<Product[]>(this.productsUrl)
.pipe(
tap(console.log),
shareReplay(),
catchError(this.handleError)
);
當Observable的streams改變時,是用推動
UI資料更新
Component
pageTitle$=this.product$
.pipe(
map((p:Product)=>
p ? `Product Detail for: ${p.productName}` : null)
);
// combine所有的streams
vm$=combineLatest(
[this.product$,this.productSuppliers$,this.pageTitle$])
.pipe( vvvvvvvvv filter掉還沒選的case
filter(([product])=>!!product),
// Array destructuring,轉成一個Object
map( ([product,productSuppliers,pageTitle]) =>
({ product,productSuppliers,pageTitle }) )
);
Template(使用)
<div *ngIf="vm$ | async as vm">
<div>{{vm.pageTitle}}</div>
<div>{{vm.product.productName}}</div>
...
<tr *ngFor="let supplier of vm.productSuppliers">
...
困難點